﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TakePhotoTogether.ImageProcessing
{
    /// <summary>
    /// The interpolation method.
    /// </summary>
    public enum Interpolation
    {
        /// <summary>
        /// The nearest neighbor algorithm simply selects the color of the nearest pixel.
        /// </summary>
        NearestNeighbor = 0,

        /// <summary>
        /// Linear interpolation in 2D using the average of 3 neighboring pixels.
        /// </summary>
        Bilinear,
    }

    public class PixelUtil
    {
        /// <summary>
        /// Resize an integer array representing a pixel array
        /// </summary>
        /// <param name="weight"></param>
        /// <param name="height"></param>
        /// <param name="pixels"></param>
        /// <param name="newWidth"></param>
        /// <param name="newHeight"></param>
        /// <param name="newPixels"></param>
        /// <param name="interpolation"></param>
        /// <returns></returns>
        public static int[] Resize(int weight, int height, int[] pixels, int newWidth, int newHeight, 
                                   int[] newPixels = null, Interpolation interpolation = Interpolation.Bilinear)
        {
            if (newPixels == null)
            {
                newPixels = new int[newWidth * newHeight];
            }

            float xs = (float)weight / newWidth;
            float ys = (float)height / newHeight;

            float fracx, fracy, ifracx, ifracy, sx, sy, l0, l1;
            int c, x0, x1, y0, y1;
            byte c1a, c1r, c1g, c1b, c2a, c2r, c2g, c2b, c3a, c3r, c3g, c3b, c4a, c4r, c4g, c4b;
            byte a = 0, r = 0, g = 0, b = 0;

            // Nearest Neighbor
            if (interpolation == Interpolation.NearestNeighbor)
            {
                var srcIdx = 0;
                for (var y = 0; y < newHeight; y++)
                {
                    for (var x = 0; x < newWidth; x++)
                    {
                        sx = x * xs;
                        sy = y * ys;
                        x0 = (int)sx;
                        y0 = (int)sy;

                        newPixels[srcIdx++] = pixels[y0 * weight + x0];
                    }
                }
            }

            // Bilinear
            else if (interpolation == Interpolation.Bilinear)
            {
                var srcIdx = 0;
                for (var y = 0; y < newHeight; y++)
                {
                    for (var x = 0; x < newWidth; x++)
                    {
                        sx = x * xs;
                        sy = y * ys;
                        x0 = (int)sx;
                        y0 = (int)sy;

                        // Calculate coordinates of the 4 interpolation points
                        fracx = sx - x0;
                        fracy = sy - y0;
                        ifracx = 1f - fracx;
                        ifracy = 1f - fracy;
                        x1 = x0 + 1;
                        if (x1 >= weight)
                            x1 = x0;
                        y1 = y0 + 1;
                        if (y1 >= height)
                            y1 = y0;


                        // Read source color
                        c = pixels[y0 * weight + x0];
                        c1a = (byte)(c >> 24);
                        c1r = (byte)(c >> 16);
                        c1g = (byte)(c >> 8);
                        c1b = (byte)(c);

                        c = pixels[y0 * weight + x1];
                        c2a = (byte)(c >> 24);
                        c2r = (byte)(c >> 16);
                        c2g = (byte)(c >> 8);
                        c2b = (byte)(c);

                        c = pixels[y1 * weight + x0];
                        c3a = (byte)(c >> 24);
                        c3r = (byte)(c >> 16);
                        c3g = (byte)(c >> 8);
                        c3b = (byte)(c);

                        c = pixels[y1 * weight + x1];
                        c4a = (byte)(c >> 24);
                        c4r = (byte)(c >> 16);
                        c4g = (byte)(c >> 8);
                        c4b = (byte)(c);


                        // Calculate colors
                        // Alpha
                        l0 = ifracx * c1a + fracx * c2a;
                        l1 = ifracx * c3a + fracx * c4a;
                        a = (byte)(ifracy * l0 + fracy * l1);

                        if (a > 0)
                        {
                            // Red
                            l0 = ifracx * c1r * c1a + fracx * c2r * c2a;
                            l1 = ifracx * c3r * c3a + fracx * c4r * c4a;
                            r = (byte)((ifracy * l0 + fracy * l1) / a);

                            // Green
                            l0 = ifracx * c1g * c1a + fracx * c2g * c2a;
                            l1 = ifracx * c3g * c3a + fracx * c4g * c4a;
                            g = (byte)((ifracy * l0 + fracy * l1) / a);

                            // Blue
                            l0 = ifracx * c1b * c1a + fracx * c2b * c2a;
                            l1 = ifracx * c3b * c3a + fracx * c4b * c4a;
                            b = (byte)((ifracy * l0 + fracy * l1) / a);
                        }

                        // Write destination
                        if (srcIdx < newPixels.Length)
                        {
                            newPixels[srcIdx++] = (a << 24) | (r << 16) | (g << 8) | b;
                        }
                    }
                }
            }
            return newPixels;
        }

        /// <summary>
        /// Crop an integer array representing a pixel matrix
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="pixels"></param>
        /// <param name="newWidth"></param>
        /// <param name="newHeight"></param>
        /// <param name="startCropPointX"></param>
        /// <param name="startCropPointY"></param>
        /// <param name="croppedPixels"></param>
        /// <returns></returns>
        public static int[] Crop(int width, int height, int[] pixels, 
                                   int newWidth, int newHeight, int startCropPointX, int startCropPointY, int[] croppedPixels = null)
        {
            if (croppedPixels == null)
            {
                croppedPixels = new int[newWidth*newHeight];
            }

            for (int j = 0; j < newHeight; j++)
            {
                for (int i = 0; i < newWidth; i++)
                {
                    croppedPixels[(j * newWidth) + i] = pixels[((j + startCropPointY) * width) + i + startCropPointX];
                }
            }

            return croppedPixels;
        }

        public static int[] RotateCropStream(int width, int height, int[] pixels,
                                   int newWidth, int newHeight, int startCropPointX, int startCropPointY, int[] croppedPixels = null)
        {
            if (croppedPixels == null)
            {
                croppedPixels = new int[newWidth * newHeight];
            }

            for (int j = 0; j < newHeight; j++)
            {
                for (int i = 0; i < newWidth; i++)
                {
                    croppedPixels[((newHeight- j - 1) * newWidth) + i] = pixels[((j + startCropPointY) * width) + i + startCropPointX];
                }
            }

            return croppedPixels;
        }



        /// <summary>
        /// Concat two integer arrays both representing pixel matrices
        /// </summary>
        /// <param name="width1"></param>
        /// <param name="height1"></param>
        /// <param name="pixels1"></param>
        /// <param name="width2"></param>
        /// <param name="height2"></param>
        /// <param name="pixels2"></param>
        /// <param name="newImagePixels"></param>
        /// <returns></returns>
        public static int[] Concat(int width1, int height1, int[] pixels1, 
                                   int width2, int height2, int[] pixels2,
                                   int[] newImagePixels = null,
                                   int image2OffsetX = 0)
        {
            int finalHeight = height1;
            int finalWidth = width1 + width2 + image2OffsetX;

            if (height2 > finalHeight)
            {
                finalHeight = height2;
            }

            if (newImagePixels == null)
            {
                newImagePixels = new int[finalHeight*finalWidth];
            }

            for (int j = 0; j < height1; j++)
            {
                for (int i = 0; i < width1; i++)
                {
                    newImagePixels[(j * finalWidth) + i] = pixels1[(j * width1) + i];
                }
            }

            for (int j = 0; j < height2; j++)
            {
                for (int i = 0; i < width2; i++)
                {
                    newImagePixels[(j * finalWidth) + i + width1 + image2OffsetX] = pixels2[(j * width2) + i];
                }
            }

            return newImagePixels;
        }


        public static int[] AddSeparatingBlackLine(int width, int height, int[] pixels)
        {
            int startx = (int)((width * 0.5) - 2);

            for (int j = 0; j < height; j++)
            {
                for (int i = startx; i < startx + 4; i++)
                {
                    pixels[(j * width) + i] = 0;
                }
            }

            return pixels;
        }
    }
}
